class Compiler {
  constructor(vm) {
    // 方便后面使用，将vm与el存储
    this.vm = vm;
    this.el = vm.$el;
    // 编译模板
    this.compiler(this.el);
  }
  // 1.遍历所有el中的节点
  compiler(el) {
    // 注意：这里使用childNodes可以获取所有子节点，而children只能获取所有子元素。这里只获取一层的子节点。
    const nodes = el.childNodes;
    // nodes是伪数组，需要转换成数组
    Array.from(nodes).forEach((node) => {
      if (this.isElementNode(node)) {
        this.compileElement(node);
      } else if (this.isTextNode(node)) {
        this.compileText(node);
      }
      // 当子节点中还有其子节点时，需要递归调用
      if (node.childNodes && node.childNodes.length) {
        this.compiler(node);
      }
    });
  }

  // 1-1 判断是否文本节点
  isTextNode(node) {
    return node.nodeType === 3;
  }

  // 1-2 判断是否元素节点
  isElementNode(node) {
    return node.nodeType === 1;
  }

  // 1-3 解析差值表达式
  compileText(node) {
    // 正则匹配 差值表达式
    const reg = /\{\{(.+?)\}\}/;
    // node.textContent和node.nodeValue都是当前节点的内容
    const content = node.textContent;
    if (reg.test(content)) {
      // 使用$1可获取正则表达式中小括号中的值
      const value = RegExp.$1.trim();
      // 差值表达式去掉双括号后，就是变量的名字，由于变量已经都在observer.js中挂载到了vm实例中，故这里取值
      node.textContent = content.replace(reg, this.vm[value]);
      // ____数据响应添加：实例化Watcher对象
      new Watcher(this.vm, value, (newValue) => {
        node.textContent = newValue;
      });
    }
  }

  // 1-4 解析指令
  // 首次渲染——只渲染v-text和v-model
  compileElement(node) {
    const attributes = node.attributes;
    Array.from(attributes).forEach((attr) => {
      let attrName = attr.name;
      if (this.isDirective(attrName)) {
        // 根据不同的指令进行不同的渲染
        // 不采取if语句，使用字符串拼接方法名的方式，便于扩展更多的指令处理方法
        attrName = attrName.substr(2); //从索引2开始截取到最后，获取到v-text指令名去除v-后的字符串。
        // 获取该属性的属性值
        const key = attr.value;
        // 解析不同的指令
        // 取值
        const reg = /\{(.+?)\}/;
        if (attrName == "on" && reg.test(key)) {
          // 需要单独为v-on处理，绑定事件
          const eventObj = {};
          RegExp.$1.split(",").forEach((item) => {
            let tmpAry = item.split(":"),
              key = tmpAry[0],
              value = tmpAry[1];
            eventObj[key] = this.vm[value];
          });
          this.bindEvent(node, eventObj);
        } else {
          this.update(node, key, attrName);
        }
      }
    });
  }
  // 1-1-1 判断是否vue指令
  isDirective(attrName) {
    return attrName.startsWith("v-");
  }
  // 负责更新Dom，update相当于一个watcher，统一执行不同指令解析的位置
  update(node, key, attrName) {
    const updaterFn = this[attrName + "Updater"];
    updaterFn && updaterFn.call(this, node, this.vm[key], key);
  }
  // v-text解析方法
  textUpdater(node, value, key) {
    node.textContent = value;
    // ____数据响应添加：实例化Watcher对象
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue;
    });
  }
  // v-model解析方法
  modelUpdater(node, value, key) {
    node.value = value;
    // ____数据响应添加：实例化Watcher对象
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue;
    });
    // ____为了实现双向绑定，页面输入的变化，同步到数据，需要定义input方法
    node.addEventListener("input", () => {
      this.vm[key] = node.value;
    });
  }
  //v-html解析方法
  htmlUpdater(node, value, key) {
    node.innerHTML = value;
    // ____数据响应添加：实例化Watcher对象
    new Watcher(this.vm, key, (newValue) => {
      node.innerHTML = newValue;
    });
  }
  //绑定事件方法
  bindEvent(node, eventObj) {
    Object.keys(eventObj).forEach((eventName) => {
      node.addEventListener(eventName, eventObj[eventName]);
    });
  }
}
